package math.visitors;

import math.BooleanEquality;
import math.Xor;
import math.ArithmeticError;
import math.LogicalBooleanVariable;
import math.Not;
import java.util.ArrayList;
import math.Add;
import math.And;
import math.BooleanConstant;
import math.BooleanVariable;
import math.Cos;
import math.Evaluation;
import math.Exp;
import math.IntegerConstant;
import math.Ln;
import math.LogicalArithmeticVariable;
import math.Mult;
import math.Negation;
import math.NumericLogicalVariable;
import math.Or;
import math.PI;
import math.Pow;
import math.Sin;
import math.Variable;
import math.Expression;
import java.util.List;
import math.ExpressionVisitor;

public class CollectByTypeVisitor<T extends Expression> implements ExpressionVisitor<List<T>>{

  private Class<T> type;
  
  private List<T> result;

  public CollectByTypeVisitor(Class<T> type){
    this.type = type;
    result = new ArrayList<T>();
  }
  
  @SuppressWarnings("unchecked")
  @Override
  public List<T> visitAnd(And and) {
    if (type.isAssignableFrom(And.class))
      result.add((T) and);
    and.getArg1().accept(this);
    and.getArg2().accept(this);
    return result;
  }

  @SuppressWarnings("unchecked")
  @Override
  public List<T> visitBooleanConstant(BooleanConstant booleanConstant) {
    if (type.isAssignableFrom(BooleanConstant.class))
      result.add((T) booleanConstant);
    return result;
  }

  @SuppressWarnings("unchecked")
  @Override
  public List<T> visitBooleanVariable(BooleanVariable booleanVariable) {
    if (type.isAssignableFrom(BooleanVariable.class))
      result.add((T) booleanVariable);
    return result;
  }

  @SuppressWarnings("unchecked")
  @Override
  public List<T> visitOr(Or or) {
    if (type.isAssignableFrom(Or.class))
      result.add((T) or);
    or.getArg1().accept(this);
    or.getArg2().accept(this);
    return result;
  }

  @SuppressWarnings("unchecked")
  @Override
  public List<T> visitXor(Xor xor) {
    if (type.isAssignableFrom(Xor.class))
      result.add((T) xor);
    xor.getArg1().accept(this);
    xor.getArg2().accept(this);
    return result;
  }

  @SuppressWarnings("unchecked")
  @Override
  public List<T> visitAdd(Add add) {
    if (type.isAssignableFrom(Add.class))
      result.add((T) add);
    add.getAddend1().accept(this);
    add.getAddend2().accept(this);
    return result;
  }

  @SuppressWarnings("unchecked")
  @Override
  public List<T> visitCos(Cos cos) {
    if (type.isAssignableFrom(Cos.class))
      result.add((T) cos);
    cos.getArgument().accept(this);
    return result;
  }

  @SuppressWarnings("unchecked")
  @Override
  public List<T> visitEvaluation(Evaluation evaluation) {
    if (type.isAssignableFrom(Evaluation.class))
      result.add((T) evaluation);
    return result;
  }

  @SuppressWarnings("unchecked")
  @Override
  public List<T> visitExp(Exp exp) {
    if (type.isAssignableFrom(Exp.class))
      result.add((T) exp);
    exp.getArgument().accept(this);
    return result;
  }

  @SuppressWarnings("unchecked")
  @Override
  public List<T> visitIntegerConstant(IntegerConstant value) {
    if (type.isAssignableFrom(IntegerConstant.class))
      result.add((T) value);
    return result;
  }

  @SuppressWarnings("unchecked")
  @Override
  public List<T> visitLn(Ln ln) {
    if (type.isAssignableFrom(Ln.class))
      result.add((T) ln);
    ln.getArgument().accept(this);
    return result;
  }

  @SuppressWarnings("unchecked")
  @Override
  public List<T> visitLogicalArithmeticVariable(LogicalArithmeticVariable logicalVariable) {
    if (type.isAssignableFrom(LogicalArithmeticVariable.class))
      result.add((T) logicalVariable);
    return result;
  }

  @SuppressWarnings("unchecked")
  @Override
  public List<T> visitLogicalBooleanVariable(LogicalBooleanVariable logicalVariable) {
    if (type.isAssignableFrom(LogicalBooleanVariable.class))
      result.add((T) logicalVariable);
    return result;
  }

  @SuppressWarnings("unchecked")
  @Override
  public List<T> visitMult(Mult mult) {
    if (type.isAssignableFrom(Mult.class))
      result.add((T) mult);
    mult.getFactor1().accept(this);
    mult.getFactor2().accept(this);
    return result;
  }

  @SuppressWarnings("unchecked")
  @Override
  public List<T> visitNegation(Negation negation) {
    if (type.isAssignableFrom(Negation.class))
      result.add((T) negation);
    negation.getArgument().accept(this);
    return result;
  }

  @SuppressWarnings("unchecked")
  @Override
  public List<T> visitNumericalLogicalVariable(
      NumericLogicalVariable numericLogicalVariable) {
    if (type.isAssignableFrom(NumericLogicalVariable.class))
      result.add((T) numericLogicalVariable);
    return result;
  }

  @SuppressWarnings("unchecked")
  @Override
  public List<T> visitPI(PI pi) {
    if (type.isAssignableFrom(PI.class))
      result.add((T) pi);
    return result;
  }

  @SuppressWarnings("unchecked")
  @Override
  public List<T> visitPow(Pow pow) {
    if (type.isAssignableFrom(Pow.class))
      result.add((T) pow);
    pow.getBase().accept(this);
    pow.getExponent().accept(this);
    return result;
  }

  @SuppressWarnings("unchecked")
  @Override
  public List<T> visitSin(Sin sin) {
    if (type.isAssignableFrom(Sin.class))
      result.add((T) sin);
    sin.getArgument().accept(this);
    return result;
  }

  @SuppressWarnings("unchecked")
  @Override
  public List<T> visitVariable(Variable var) {
    if (type.isAssignableFrom(Variable.class))
      result.add((T) var);
    return result;
  }

  @SuppressWarnings("unchecked")
  @Override
  public List<T> visitNot(Not not) {
    if (type.isAssignableFrom(Not.class))
      result.add((T) not);
    not.getArgument().accept(this);
    return result;
  }

  @SuppressWarnings("unchecked")
  @Override
  public List<T> visitArithmeticError(ArithmeticError arithmeticError) {
    if (type.isAssignableFrom(ArithmeticError.class))
      result.add((T)arithmeticError);
    return result;
  }

  @SuppressWarnings("unchecked")
  @Override
  public List<T> visitBooleanEquality(BooleanEquality booleanEquality) {
    if (type.isAssignableFrom(BooleanEquality.class))
      result.add((T) booleanEquality);
    booleanEquality.getArg1().accept(this);
    booleanEquality.getArg2().accept(this);
    return result;
  }
}